#include "Common.h"

#if defined _WIN32

#include "WindowsApp.h"
#include <Windows.h>

using namespace RayFrame;
using namespace glm;

DLL_DECLARE WindowsApp* RayFrame::g_winApp = NULL;

bool active;
HCURSOR hCurs;

#define GETX(l) (int(l & 0xFFFF))
#define GETY(l) (int(l) >> 16)

LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  assert(g_App && g_winApp && g_App == g_winApp);

  WindowsApp::VecCallback& callbacks = g_winApp->GetCallbacks();
  for(WindowsApp::VecCallback::iterator iter = callbacks.begin();
    iter != callbacks.end(); iter++)
  {
    (*iter)(hwnd, message, wParam, lParam);
  }

  bool defaultProcess = false;
  switch (message)
  {
  case WM_PAINT:
    PAINTSTRUCT paint;
    BeginPaint(hwnd, &paint);
    active = !IsRectEmpty(&paint.rcPaint);
    EndPaint(hwnd, &paint);
    break;
  case WM_MOUSEMOVE:
    static int lastX, lastY;
    int x, y;
    x = GETX(lParam);
    y = GETY(lParam);
    defaultProcess = g_App->OnMouseMove(x, y, x - lastX, y - lastY);
    lastX = x;
    lastY = y;
    SetCursor(hCurs);
    break;
  case WM_KEYDOWN:
    defaultProcess = g_App->OnKey((unsigned int) wParam, true);
    break;
  case WM_KEYUP:
    defaultProcess = g_App->OnKey((unsigned int) wParam, false);
    break;
  case WM_LBUTTONDOWN:
    SetCapture(hwnd);
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_LEFT, true);
    break;
  case WM_LBUTTONUP:
    ReleaseCapture();
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_LEFT, false);
    break;
  case WM_RBUTTONDOWN:
    SetCapture(hwnd);
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_RIGHT, true);
    break;
  case WM_RBUTTONUP:
    ReleaseCapture();
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_RIGHT, false);
    break;
  case WM_MBUTTONDOWN:
    SetCapture(hwnd);
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_MIDDLE, true);
    break;
  case WM_MBUTTONUP:
    ReleaseCapture();
    g_App->OnMouseButton(GETX(lParam), GETY(lParam), MOUSE_MIDDLE, false);
    break;
  case WM_MOUSEWHEEL:
    static int scroll;
    int s;

    scroll += GET_WHEEL_DELTA_WPARAM(wParam);
    s = scroll / WHEEL_DELTA;
    scroll %= WHEEL_DELTA;

    POINT point;
    point.x = GETX(lParam);
    point.y = GETY(lParam);
    ScreenToClient(hwnd, &point);

    if (s != 0) 
      g_App->OnMouseWheel(point.x, point.y, s);
    break;
  case WM_SIZE:
    g_App->OnResize(LOWORD(lParam), HIWORD(lParam));
    break;
  case WM_WINDOWPOSCHANGED:
    if ((((LPWINDOWPOS) lParam)->flags & SWP_NOSIZE) == 0){
      RECT rect;
      GetClientRect(hwnd, &rect);
      int w = rect.right - rect.left;
      int h = rect.bottom - rect.top;
      if (w > 0 && h > 0) g_App->OnResize(w, h);
    }
    break;
  case WM_SYSKEYDOWN:
    if ((lParam & (1 << 29)) && wParam == KEY_ENTER){
      //app->toggleFullscreen();
    } else {
      defaultProcess = g_App->OnKey((unsigned int) wParam, true);
    }
    break;
  case WM_SYSKEYUP:
    defaultProcess = g_App->OnKey((unsigned int) wParam, false);
    break;
  case WM_CREATE:
    ShowWindow(hwnd, SW_SHOW);
    break;
  case WM_CLOSE:
    g_App->CloseWindow(true, true);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hwnd, message, wParam, lParam);
  }

  if(defaultProcess)
    DefWindowProc(hwnd, message, wParam, lParam);

  return 0;
}

WindowsApp::WindowsApp(void):
m_inst(NULL)
{
  assert(!g_winApp);
  g_winApp = this;
}

WindowsApp::~WindowsApp(void)
{
#ifdef _DEBUG
  _CrtDumpMemoryLeaks();
#endif
}

void WindowsApp::InitApp()
{
  assert(m_inst);

  /*
    Force the main thread to always run on CPU 0.
    This is done because on some systems QueryPerformanceCounter returns a bit different counter values
    on the different CPUs (contrary to what it's supposed to do), which can cause negative frame times
    if the thread is scheduled on the other CPU in the next frame. This can cause very jerky behavior and
    appear as if frames return out of order.
  */
  SetThreadAffinityMask(GetCurrentThread(), 1);

  AppBase::InitApp();

  g_winApp->OnResize( (int)m_frame->GetSize().x, (int)m_frame->GetSize().y );
}

FrameBase* RayFrame::WindowsApp::InitFrame()
{
  return new WindowsSimpleFrame();
}

void WindowsApp::SetInitParams( HINSTANCE hThisInst, HINSTANCE hLastInst, LPSTR lpszCmdLine, int nCmdShow )
{
  m_inst = hThisInst;
  m_cmdArguments = lpszCmdLine;
}

void WindowsApp::StartLoop()
{
  MSG msg;

  //ResetCamera();
  hCurs = LoadCursor(NULL, IDC_ARROW);
  SetCursor(hCurs);

  while (!IsDone())
  {

    InvalidateRect(((WindowsSimpleFrame*)m_frame)->GetHWnd(), NULL, FALSE);

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
      //TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    if (msg.message == WM_QUIT) 
      break;

    if (active && !IsDone())
    {
      g_App->Tick();
    } 
    else
    {
      Sleep(100);
    }
  }

  Exit();
}

void WindowsApp::Exit()
{

}

RayFrame::WindowsSimpleFrame::WindowsSimpleFrame()
{
  internalCreate();
}

RayFrame::WindowsSimpleFrame::WindowsSimpleFrame(vec2 position, vec2 size, STRING caption):
FrameBase(position, size, caption)
{
  internalDestroy();
}

void RayFrame::WindowsSimpleFrame::internalCreate()
{
  const char* className = "RayFrame Window Class";
  WNDCLASS wincl;
  wincl.hInstance = ((WindowsApp*)g_App)->GetInstance();
  wincl.lpszClassName = className;
  wincl.lpfnWndProc = WinProc;
  wincl.style = 0;
  wincl.hIcon = NULL;
  wincl.hCursor = NULL;
  wincl.lpszMenuName = NULL;
  wincl.cbClsExtra = 0;
  wincl.cbWndExtra = 0;
  wincl.hbrBackground = NULL;
  if (!RegisterClass(&wincl)) 
  {
    m_isOK = false;
    return;
  }

  int PixelFormat;            // Holds The Results After Searching For A Match
  RECT windowRect;
  windowRect.left = (long)GetPosition().x;
  windowRect.right= windowRect.left + (long)GetSize().x;
  windowRect.top = (long)GetPosition().y;
  windowRect.bottom = windowRect.top + (long)GetSize().y;

  DWORD    dwExStyle;            // Window Extended Style
  DWORD    dwStyle;

  dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;      // Window Extended Style
  dwStyle=WS_OVERLAPPEDWINDOW;

  AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);    // Adjust Window To True Requested Size

  if (!(m_hwnd=CreateWindowEx(  dwExStyle,                // Extended Style For The Window
    className,                                            // Class Name
    GetCaption().c_str(),                                 // Window Title
    WS_CLIPSIBLINGS |                                     // Required Window Style
    WS_CLIPCHILDREN |                                     // Required Window Style
    dwStyle,                                              // Selected Window Style
    (int)GetPosition().x, (int)GetPosition().y,           // Window Position
    windowRect.right - windowRect.left,                   // Calculate Adjusted Window size
    windowRect.bottom - windowRect.top,
    NULL,                                                 // No Parent Window
    NULL,                                                 // No Menu
    g_winApp->GetInstance(),                              // Instance
    NULL)))                                               // Don't Pass Anything To WM_CREATE
  {
    ErrorMsg("Error while creating window, exiting.");
    m_isOK = false;
    return;
  }

  static PIXELFORMATDESCRIPTOR pfd=          // pfd Tells Windows How We Want Things To Be
  {
    sizeof(PIXELFORMATDESCRIPTOR),          // Size Of This Pixel Format Descriptor
    1,                // Version Number
    PFD_DRAW_TO_WINDOW |            // Format Must Support Window
    PFD_SUPPORT_OPENGL |            // Format Must Support OpenGL
    PFD_DOUBLEBUFFER,            // Must Support Double Buffering
    PFD_TYPE_RGBA,              // Request An RGBA Format
    24,                // Select Our Color Depth
    0, 0, 0, 0, 0, 0,            // Color Bits Ignored
    0,                // No Alpha Buffer
    0,                // Shift Bit Ignored
    0,                // No Accumulation Buffer
    0, 0, 0, 0,              // Accumulation Bits Ignored
    16,                // 16Bit Z-Buffer (Depth Buffer)
    0,                // No Stencil Buffer
    0,                // No Auxiliary Buffer
    PFD_MAIN_PLANE,              // Main Drawing Layer
    0,                // Reserved
    0, 0, 0                // Layer Masks Ignored
  };

  if (!(m_hdc=GetDC( m_hwnd )))              // Did We Get A Device Context?
  {
    ErrorMsg("Can't Create A GL Device Context.");
    m_isOK = false;
    return;              // Return FALSE
  }

  if (!(PixelFormat=ChoosePixelFormat(m_hdc,&pfd)))        // Did Windows Find A Matching Pixel Format?
  {
    ErrorMsg("Can't Find A Suitable PixelFormat.");
    m_isOK = false;
    return;
  }

  if(!SetPixelFormat(m_hdc,PixelFormat,&pfd))        // Are We Able To Set The Pixel Format?
  {
    ErrorMsg("Error while setting pixel format.");
    m_isOK = false;
    return;
  }

  if (!(m_hrc=wglCreateContext(m_hdc)))          // Are We Able To Get A Rendering Context?
  {
    ErrorMsg("Can't create WGL context.");
    m_isOK = false;
    return;
  }

  if(!wglMakeCurrent(m_hdc,m_hrc))            // Try To Activate The Rendering Context
  {
    ErrorMsg("Can't make current rendering context.");
    m_isOK = false;
    return;
  }

  ShowWindow(m_hwnd,SW_SHOW);            // Show The Window
  SetForegroundWindow(m_hwnd);          // Slightly Higher Priority
  SetFocus(m_hwnd);                      // Sets Keyboard Focus To The Window
}

void RayFrame::WindowsSimpleFrame::internalDestroy()
{
  wglMakeCurrent(NULL, NULL);
  wglDeleteContext(m_hrc);
  ReleaseDC(m_hwnd, m_hdc);

  DestroyWindow(m_hwnd);
}

void RayFrame::WindowsSimpleFrame::SwapBuffer()
{
  ::SwapBuffers(m_hdc);
}

void RayFrame::WindowsApp::RegisterWindowsEventCallback( WindowsEventCallback cb )
{
  m_eventCallbacks.push_back(cb);
}

#endif
